package lru

import (
	"container/list"
	"fmt"
)

// 作者大量使用了结构体来定义一种功能。

type Cache struct {
	maxBytes int64 // 允许使用的最大内存
	nbytes int64 // 当前已经使用的内存
	// go语言标准库实现的双向链表
	ll *list.List
	// 双向链表中对应节点的指针
	cache map[string]*list.Element

	OnEvicted func(key string, value Value) // 某条记录被移除时触发的回调函数
}

type entry struct {
	key string // 记录对应的字典中的 key
	// 双向链表单个节点中存储的数据类型
	// 是 接口形式的
	// 任何实现了此接口的类型，都可以存储到链表中
	// 需要实现 Len() 方法
	value Value
}

type Value interface {
	Len() int
}

// 返回数据总数
func (c *Cache) Len() int {

	return c.ll.Len()
}

// 实例化缓存
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
	return &Cache{
		maxBytes: maxBytes,
		ll: list.New(),
		cache: make(map[string]*list.Element),
		OnEvicted: onEvicted,
	}
}
// 查找缓存
func (c *Cache) Get(key string) (value Value, ok bool) {
	if ele, ok := c.cache[key]; ok {
		// 将ele移动到ll前面，表示此缓存最近被访问过
		c.ll.MoveToFront(ele)
		// 判断 ele.Value 是否是指向 *entry类型的变量，如果不是则报错
		// 如果是则转化为 *entry类型并赋值给 kv
		// ele.Value是空接口类型，可以是任意类型

		kv := ele.Value.(*entry)
		return kv.value, true
	}
	return
}
// 删除 移除队首的节点，即最近最少访问的节点
func(c *Cache) RemoveOldest() {
	ele := c.ll.Back() // 取到队首，从链表中删除
	if ele != nil {
		c.ll.Remove(ele)

		kv := ele.Value.(*entry)
		delete(c.cache, kv.key) // 从map映射中删除对应的数据

		// 减去 key value占用内存
		// key为 string 类型， value为接口类型，含Len()方法
		// 更新当前所用的内存
		c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
		// 回调函数
		if c.OnEvicted != nil {
			c.OnEvicted(kv.key, kv.value)
		}
	}

}
// 新增 和 修改
func (c *Cache) Add(key string, value Value) {
	if ele, ok := c.cache[key]; ok {
		// 如果已经存在此数据，则将其放到队尾，表示此数据最近被访问过
		c.ll.MoveToFront(ele)
		// 拿到当前 key 对应节点的值
		kv := ele.Value.(*entry)

		// 存储的数据占用的内存需要更新
		// 加 新增 value的长度，减去老value占用的长度
		c.nbytes += int64(value.Len()) - int64(kv.value.Len())
		// kv 是指针类型，对其修改影响到节点中存储的数据
		// 更新数据
		kv.value = value
	} else {
		// 加入一个新的数据的缓存
		ele := c.ll.PushFront(&entry{key, value})
		c.cache[key] = ele
		c.nbytes += int64(len(key)) + int64(value.Len())
	}
	// 当更新操作完毕，检查内存是否够用，不够则删除旧缓存
	// 这是一个循环，存在删除多个队首节点的可能性
	for c.maxBytes != 0 && c.maxBytes < c.nbytes {
		fmt.Println("删除旧缓存。")
		c.RemoveOldest()
	}
}